#include "clientnntp.h"
#include <iostream>
#include <cstdlib>

#define trace if(false) cout

using namespace std;

bool ClientNNTP::receive(string& input, size_t n)
{
	char c;
	input = "";
	try {
		trace << "Receive: ";
		for (size_t i = 0; i < n; ++ i) {
			c = connection->read();
			trace << (int)c << " ";
			input += c;
		}
		trace << endl;
	} catch (ConnectionClosedException e) {
		trace << "ConnectionClosedException" << endl;
		throw ConnectionClosedException();
	}
	return true;
}

bool ClientNNTP::connect(const char* host, int port)
{
	connection = new Connection(host, port);
	if (connection->isConnected())
		return true;
	return false;
}

ClientNNTP::ClientNNTP() :
	connection(NULL)
{

}

ClientNNTP::~ClientNNTP()
{
	if (connection != NULL) {
		delete connection;
	}
}

bool ClientNNTP::send(string& msg)
{
	size_t i;

	try {
		trace << "Send: ";
		for (i = 0; i < msg.size(); ++i) {
			trace << (int)msg[i] << " ";
			connection->write(msg[i]);
		}
		trace << endl;
	} catch (ConnectionClosedException e) {
		return false;
	}
	return true;
}

bool ClientNNTP::listArticles(vector< pair<unsigned int, string> >& ans, unsigned int& newsgroup)
{
	string tmp, tmp2, tmp3;
	string s;
	unsigned int i, id;
	s += Protocol::COM_LIST_ART;
	s += Protocol::PAR_NUM;
	s += int_to_string(newsgroup);
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "listArticles: Send fail" << endl;
		return false;
	}

	if (!receive(s, 2)) {
		trace << "listArticles: Receive fail" << endl;
		return false;
	}
	// Parse ans.
	if (s[0] != Protocol::ANS_LIST_ART) {
		trace << "listArticles: Ans != ANS_LIST_ART" << endl;
		throw ConnectionClosedException();
	}

	if (s[1] != Protocol::ANS_ACK) {
		trace << "listArticles: Ans NAK" << endl;
		receive(tmp, 2);
		return false;
	}
	

	if (!parseParNum(tmp)) {
		trace << "listArticles: parseParNum fail" << endl;
		throw ConnectionClosedException();
	}

	// Start at first article
	for (i = 0; i < string_to_int(tmp); ++i) {
		// Get ID
		if (!parseParNum(tmp2)) {
			trace << "listArticles: parseParNum fail 2" << endl;
			throw ConnectionClosedException();
		}
		id = string_to_int(tmp2);
		
		if (!parseParString(tmp2)) {
			trace << "listArticles: parseParString fail" << endl;
			throw ConnectionClosedException();
		}

		// Extract news group name and add to ans.
		ans.push_back(make_pair(id, tmp2));
	}

	receive(tmp, 1);
	return true;
}

bool ClientNNTP::createArticle(string& title, string& author, string& text, unsigned int& newsgroup)
{
	string s;
	unsigned int n;
	s += Protocol::COM_CREATE_ART;
	s += Protocol::PAR_NUM;
	s += int_to_string(newsgroup);
	s += Protocol::PAR_STRING;
	n = static_cast<unsigned int>(title.size());
	s += int_to_string(n);
	s += title;
	s += Protocol::PAR_STRING;
	n = static_cast<unsigned int>(author.size());
	s += int_to_string(n);
	s += author;
	s += Protocol::PAR_STRING;
	n = static_cast<unsigned int>(text.size());
	s += int_to_string(n);
	s += text;
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "createArticle: send fail." << endl;
		return false;
	}

	s = "";
	if (!receive(s, 3)) {
		trace << "createArticle: receive fail." << endl;
		return false;
	}

	if (s[0] != Protocol::ANS_CREATE_ART) {
		trace << "createArticle: s != ANS_CREATE_ART" << endl;
		throw ConnectionClosedException();
	}

	if (s[1] != Protocol::ANS_ACK) {
		trace << "createArticle: Not ANS_ACK" << endl;
		receive(s, 1);
		return false;
	}
	
	return true;
}

bool ClientNNTP::deleteArticle(unsigned int& article, unsigned int& newsgroup)
{
	string s;
	s += Protocol::COM_DELETE_ART;
	s += Protocol::PAR_NUM;
	s += int_to_string(newsgroup);
	s += Protocol::PAR_NUM;
	s += int_to_string(article);
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "deleteArticle: send fail." << endl;
		return false;
	}

	s = "";
	if (!receive(s, 3)) {
		trace << "deleteArticle: recieve fail." << endl;
		return false;
	}

	if (s[0] != Protocol::ANS_DELETE_ART) {
		trace << "deleteArticle: s != ANS_DELETE_ART" << endl;
		throw ConnectionClosedException();
	}

	if (s[1] != Protocol::ANS_ACK) {
		trace << "deleteArticle: Not ANS_ACK" << endl;
		receive(s, 1);
		return false;
	}

	return true;
}

bool ClientNNTP::createNewsGroup(string& name)
{
	string s;
	unsigned int n;
	s += Protocol::COM_CREATE_NG;
	s += Protocol::PAR_STRING;
	n = static_cast<unsigned int>(name.size());
	s += int_to_string(n);
	s += name;
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "createNewsGroup: send fail." << endl;
		return false;
	}
	
	if (!receive(s, 3)) {
		trace << "createNewsGroup: receive fail." << endl;
		return false;
	}

	if (s[0] != Protocol::ANS_CREATE_NG) {
		trace << "createNewsGroup: s != ANS_CREATE_NG." << endl;
		throw ConnectionClosedException();
	}

	if (s[1] != Protocol::ANS_ACK) {
		trace << "createNewsGroup: Not ANS_ACK." << endl;
		receive(s, 1);
		return false;
	}

	return true;
}

bool ClientNNTP::showArticle(vector<string>& res, unsigned int& article, unsigned int& newsgroup)
{
	string s;
	string tmp;
	string tmp2;
	s += Protocol::COM_GET_ART;
	s += Protocol::PAR_NUM;
	s += int_to_string(newsgroup);
	s += Protocol::PAR_NUM;
	s += int_to_string(article);
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "showArticle: send fail." << endl;
		return false;
	}

	if (!receive(s, 2)) {
		trace << "showArticle: receive fail." << endl;
		return false;
	}

	if (s[0] != Protocol::ANS_GET_ART) {
		trace << "showArticle: s != ANS_GET_ART" << endl;
		throw ConnectionClosedException();
	}

	if (s[1] != Protocol::ANS_ACK) {
		trace << "showArticle: s != ANS_ACK" << endl;
		receive(s, 2);
		return false;
	}

	// Title
	if (!parseParString(s)) {
		trace << "showArticle: parseParString fail 1" << endl;
		throw ConnectionClosedException();
	}
	res.push_back(s);
	
	// Author
	if (!parseParString(s)) {
		trace << "showArticle: parseParString fail 2" << endl;
		throw ConnectionClosedException();
	}
	res.push_back(s);

	// Text
	if (!parseParString(s)) {
		trace << "showArticle: parseParString fail 3" << endl;
		throw ConnectionClosedException();
	}
	res.push_back(s);
	receive(s, 1);
	return true;
}

bool ClientNNTP::deleteNewsGroup(unsigned int& name)
{
	string s;
	s += Protocol::COM_DELETE_NG;
	s += Protocol::PAR_NUM;
	s += int_to_string(name);
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "deleteNewsGroup: send fail." << endl;
		return false;
	}

	if (!receive(s, 3)) {
		trace << "deleteNewsGroup: receive fail." << endl;
		return false;
	}

	if (s[0] != Protocol::ANS_DELETE_NG) {
		trace << "deleteNewsGroup: s != ANS_DELETE_NG" << endl;
		throw ConnectionClosedException();
	}

	if (s[1] == Protocol::ANS_NAK) {
		trace << "deleteNewsGroup: ANS_NAK." << endl;
		receive(s, 1);
		return false;
	}
	return true;
}

bool ClientNNTP::parseParNum(string& dest)
{
	if (!receive(dest, 1)) {
		trace << "parseParNum: receive fail." << endl;
		return false;
	}

	if (dest[0] != Protocol::PAR_NUM) {
		trace << "parseParNum: in != PAR_NUM" << endl;
		return false;
	}
	trace << "Before num" << endl;
	if (!receive(dest, 4)) {
		trace << "parseParNum: receive fail 2" << endl;
		return false;
	}
	trace << "After num" << endl;

/*	if (!parse_N(dest, in))  {
		trace << "parseParNum: parse_N fail" << endl;
		return false;
	}*/

	return true;
}

bool ClientNNTP::parseParString(string& dest)
{
//	string tmp;
	string in;
	dest = "";
trace << "Parse parstring" << endl;
	if (!receive(in, 1)) {
		trace << "parseParString: receive fail 1" << endl;
		return false;
	}

	if (in[0] != Protocol::PAR_STRING) {
		trace << "parseParString: in != PAR_STRING" << endl;
		return false;
	}
	
	if (!receive(in, 4)) {
		trace << "parseParString: receive fail 2" << endl;
		return false;
	}

/*	if (!parse_N(tmp, in)) {
		trace << "parseParString: parse_N fail" << endl;
		return false;
	}*/
	
	if (!receive(dest, string_to_int(in))) {
		trace << "parseParString: receive fail 3" << endl;
		return false;
	}

	return true;
}

bool ClientNNTP::listNewsGroups(vector< pair<unsigned int, string> >& ans)
{
	string tmp, tmp2;
	string s;
	unsigned int i, id;

	s += Protocol::COM_LIST_NG;
	s += Protocol::COM_END;

	if (!send(s)) {
		trace << "listNewsGroups: Send fail" << endl;
		return false;
	}

	if (!receive(s, 1)) {
		trace << "listNewsGroups: Receive fail 1" << endl;
		return false;
	}

	// Parse ans.
	if (s[0] != Protocol::ANS_LIST_NG) {
		trace << "listNewsGroups: Ans != ANS_LIST_NG" << endl;
		return false;
	}

	if (!parseParNum(tmp)) {
		trace << "listNewsGroups: parseParNum fail" << endl;
		throw ConnectionClosedException();
	}

	for (i = 0; i < string_to_int(tmp); ++i) {
		// Get ID
		if (!parseParNum(tmp2)) {
			trace << "listNewsGrousp: parseParNum fail 2" << endl;
			throw ConnectionClosedException();
		}
		id = string_to_int(tmp2);

		if (!parseParString(tmp2)) {
			trace << "listNewsGroups: parseParString fail" << endl;
			throw ConnectionClosedException();
		}

		// Extract news group name and add to ans.
		ans.push_back(make_pair(id, tmp2));
	}

	receive(tmp2, 1);
	return true;
}
